﻿/*
ParallelPlatform Project - A web platform to distribute and share knowledge
Online Editor - An online tool to create and play animations
Copyright (C) 2011 Daniel Guerrero Martínez & Sergio Rodríguez Lumley

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package Classes{

	import flash.display.MovieClip;
	import flash.display.DisplayObject;
	import flash.events.MouseEvent;
	import flash.text.TextFormat;
	import flash.text.AntiAliasType;
	import fl.controls.TextInput;
	import flash.geom.Rectangle;
	import flash.geom.Point;
	
	public class CanvasCore {
		
		private var canvas:Canvas;
		
		private var current:uint; // Current container
		private var containers:Vector.<Container>;
		private var blockPanel:Panel; // BlockPanel stops pointer to select objects when inserting
		private var backPanel:Panel; // Used to unselect Shapes
		private var showDepthTool:Boolean;
		private var shapeShadows:ShapeShadowList;
		private var input_txt:TextInput;
		private var widgets:Vector.<DisplayObject>;
		private var selectedShapes:		Vector.<ShapeObject>; // List of selected shapes
		private var selectionRects:		Vector.<SelectionRect>; // List of selectionRects
		private var mainSelectionRect:	MovieClip;
		
		private var changeToolFunction:Function; // Function to request the another tool (ToolInfo.TOOL)
		private var undoFunction:Function; // Function to call when there's something (or not) to undo, has 1 parameter (Boolean)
		private var redoFunction:Function; // Function to call when there's something (or not) to redo, has 1 parameter (Boolean)
		
		public function CanvasCore(canv:Canvas){
			canvas = canv;
			
			mainSelectionRect	= new MovieClip();
			selectedShapes 	= new Vector.<ShapeObject>();
			selectionRects 	= new Vector.<SelectionRect>();
			input_txt 		= new TextInput();
			widgets			= new Vector.<DisplayObject>();
			containers 		= new Vector.<Container>();
			backPanel 		= new Panel();
			blockPanel 		= new Panel();
			shapeShadows	= new ShapeShadowList();
			current 		= 0;
			input_txt.setStyle("upSkin", new TL_DisplayBlock());
			input_txt.restrict = ShapeObjectDatabase.getRestrictedCharacters();
			input_txt.textField.multiline	= true;
			input_txt.textField.wordWrap	= true;
			input_txt.textField.embedFonts	= true;
			input_txt.textField.antiAliasType = AntiAliasType.ADVANCED;

			backPanel.name 	= "backPanel";
			blockPanel.name = "blockPanel";
			blockPanel.mouseChildren 	= false;
			blockPanel.buttonMode 		= true;
			blockPanel.useHandCursor	= true;
			mainSelectionRect.mouseChildren = false;
			
			canvas.addChild(blockPanel);
			showBlockPanel(false);
			
			// Adding eventListeners
			backPanel.addEventListener(MouseEvent.CLICK, canvas.clickBack);
			blockPanel.addEventListener(MouseEvent.CLICK, canvas.clickFront);
		}
		
		/**
		  Adds the specified shape to the selection list
		  @param obj ShapeObject being selected
		*/
		public function addShapeToSelection(obj:ShapeObject):void{
			selectedShapes.push(obj);
			selectionRects.push(new SelectionRect(obj));
			if(selectedShapes.length == 1){
				this.addChild(mainSelectionRect);
			}
			this.addChild(selectionRects[selectionRects.length-1]);
			refreshMainSelectionRect();
			//appearDepthTool();
		}
		
		public function changeTool(t:uint):void{
			changeToolFunction(t);
		}
		
		/**
		  Creates and adds a new container to the containers vector.
		  If it is the first container to be added, it adds it to the displayList
		  automatically and sets the blockPanel on top. This also would modify
		  the value of current (current container).
		*/
		public function createContainer():void{
			if(containers.length <= 0) containers.push(new Container(0));
			else{
				containers.push(containers[containers.length-1].duplicate());
				containers[containers.length-1].setID(containers.length-1);
			}
			
			if(containers.length == 1){
				current = uint.MAX_VALUE; // This way select container won't attempt to remove elements from last selected container
				canvas.selectContainer(0);
			}
		}
		
		/**
		  Sets the undo and redo functions to be called when there is an action
		  to undo (or not), or to redo (or not)
		  @param undF Function to undo action, with one parameter (Boolean)
		  @param redF Function to redo action, with one parameter (Boolean)
		*/
		public function setUndoRedoFunctions(undF:Function, redF:Function):void{
			undoFunction = undF;
			redoFunction = redF;
		}
		
		/**
		  Selects the specified container to show in the canvas and edit
		  @param c Int specifying the container to select
		*/
		public function selectContainer(c:uint):void{
			// First we remove all information regarding last selected container
			if(current < containers.length){
				this.removeSelections();
				//this.commitTextEditing();
				
				containers[current].removeBackPanel();
				containers[current].cleanAll();
				canvas.removeChild(containers[current]);
			}
			
			// Now we add everything to the new one
			current = c;
			containers[current].addBackPanel(backPanel);
			
			// Get info about shape shadows here
			fillShadowList();
			repaint();
			canvas.addChild(containers[current]);
			containers[current].setSelectionFunction(canvas.clickShape);
			this.checkUndoRedoActions();
			
			// We put blockPanel over the container recently selected
			canvas.setChildIndex(blockPanel, canvas.numChildren-1);
			
			// Now we reorder all added widgets over the recently selected
			// container, so these will be on top of everything
			for each(var w:DisplayObject in widgets){
				canvas.setChildIndex(w, canvas.numChildren-1);
			}
		}
		
		/**
		  Returns the number of scenes (called containers here)
		*/
		public function getNumContainers():uint{
			return containers.length;
		}
		
		/**
		  Return the back panel,
		  which triggers the backClick
		  @return Panel backPanel
		*/
		public function getBackPanel():Panel{
			return backPanel;
		}
		
		/**
		  Return the block panel, also known as "front panel",
		  which triggers the frontClick
		  @return Panel blockPanel
		*/
		public function getBlockPanel():Panel{
			return blockPanel;
		}
		
		/**
		  Returns a reference to the vector of containers
		*/
		public function getContainers():Vector.<Container>{
			return containers;
		}
		
		/**
		  Returns the index of the current container
		*/
		public function getCurrent():uint{
			return current;
		}
		
		/**
		  Returns the specified container, use CanvasCore.current to
		  get the current container being shown
		  @param c uint index of the container to get
		*/
		public function getContainer(c:uint):Container{
			return containers[c];
		}
		
		/**
		  Returns the main selection rect
		*/
		public function getMainSelectionRect():MovieClip{
			return mainSelectionRect;
		}
		
		/**
		  Returns a list of uint from selectedShapes list ordered as specified, the list (vector)
		  is ordered as specified and each position contains a reference to the original position
		  of the vector selectedShapes
		  @param decreasingOrder Boolean that indicates how to order, true to return the vector in decreasing order, otherwise false
		  @return Vector<uint> ordered list with references to the original
		*/
		public function getOrderedSelectedReferences(decreasingOrder:Boolean):Vector.<uint>{
			var ret:Vector.<uint> = new Vector.<uint>(selectedShapes.length);
			var tmp:uint = 0;
			var i:uint = 0;
			var j:uint = 0;
			
			// Fill the vector with the ordered list
			for(i=0; i<ret.length; ++i)
				ret[i] = i;
			
			if(decreasingOrder){
				for(i = 0; i< ret.length; ++i){
					for(j = 0; j < ret.length -1 - i; ++j){
						if(containers[current].getShapeDepth(selectedShapes[ret[j]]) <
							containers[current].getShapeDepth(selectedShapes[ret[j+1]]) ){
							tmp = ret[j];
							ret[j] = ret[j+1];
							ret[j+1] = tmp;
						}
					}
				}
			}else{
				for(i = 0; i< ret.length; ++i){
					for(j = 0; j < ret.length -1 - i; ++j){
						if(containers[current].getShapeDepth(selectedShapes[ret[j]]) >
							containers[current].getShapeDepth(selectedShapes[ret[j+1]]) ){
							tmp = ret[j];
							ret[j] = ret[j+1];
							ret[j+1] = tmp;
						}
					}
				}
			}
			return ret;
		}
		
		/**
		  Returns the vector of selection rectangles
		*/
		public function getSelectionRects():Vector.<SelectionRect>{
			return selectionRects;
		}
		
		/**
		  Returns the vector of selected shapes
		*/
		public function getSelectedShapes():Vector.<ShapeObject>{
			return selectedShapes;
		}
		
		/**
		  Gets the shadow of the specified shapeObject in the current canvas,
		  null if it doesn't have any. Note a shadow is the position (size and rotation)
		  of the object in the last frame.
		  @param shap ShapeObject to get its' shadow
		  @param ShapeObject Shadow of the Shape
		*/
		public function getShapeShadow(shap:ShapeObject):ShapeObject{
			return shapeShadows.getShadow(shap);
		}
		
		/**
		  Returns the input text object of the core, use this object
		  when you need user input. It's attributes can be modified
		  at will, just remember to add it as a child
		*/
		public function getInputTxt():TextInput{
			return input_txt;
		}
		
		/**
		  Checks for consistency between all containers, from the one
		  selected right now to the end, creating shapes, destroying,
		  moving them... depending on the actions done
		  @param buildAll Boolean indicates if its necessary to build the whole
		  			animation (used in readXML)
		*/
		public function buildContainers(buildAll:Boolean = false):void{
			var i:uint = current+1; // From the current container to all the next ones
			var lastContainer:Container = containers[current];
			var currentActionList:ActionList;
			var currentExtras:Array;
			
			if(buildAll){
				current = 0;
				i = 1;
				containers[0].removeAll();
				containers[0].applyActions();
				lastContainer = containers[0];
			}
			
			//trace(containers[current].getActions().printString());
			for(; i< containers.length; ++i){
				currentActionList 	= containers[i].getActions();
				currentExtras 		= containers[i].getExtras();
				containers[i].removeAll();
				containers[i] 		= lastContainer.duplicate();
				containers[i].setID(i);
				containers[i].setExtras(currentExtras);
				containers[i].setActions(currentActionList);
				containers[i].applyActions();
				lastContainer 		= containers[i];
			}
		}
		
		/**
		  Refreshes the size of the main selection rect according to the number of
		  selection rects
		*/
		public function refreshMainSelectionRect():void{
			if(selectionRects.length > 0){
				var minX:Number = Number.MAX_VALUE;
				var minY:Number = Number.MAX_VALUE;
				var maxX:Number = Number.MIN_VALUE;
				var maxY:Number = Number.MIN_VALUE;
				
				var rect:Rectangle;
				for each (var sel:ShapeObject in selectedShapes){
					rect = sel.getBounds(sel.parent);
					if(rect.x < minX) minX = rect.x;
					if(rect.x+rect.width > maxX) maxX = rect.x+rect.width;
					if(rect.y < minY) minY = rect.y;
					if(rect.y+rect.height > maxY) maxY = rect.y+rect.height;
				}
				
				mainSelectionRect.x = (minX + maxX) /2;
				mainSelectionRect.y = (minY + maxY) /2;
				minX = maxX - mainSelectionRect.x; // Width
				minY = maxY - mainSelectionRect.y; // Height
			
				mainSelectionRect.graphics.clear();
				mainSelectionRect.graphics.lineStyle(3,0xFFAA00);
				mainSelectionRect.graphics.drawRect(
						-minX,
						-minY,
						 minX*2,
						 minY*2);
			}
		}
		
		/**
		  Sets the type of each container given a list of types
		  @param types Vector.<uint> with each type of Container.type
		*/
		public function setContainersTypes(types:Vector.<uint>):void{
			for(var i:uint=0; i<containers.length; ++i){
				if(containers[i].getType() != types[i])
					containers[i].setType(types[i]);
			}
		}
		
		/**
		  Pops the last container (does not check if it is possible)
		*/
		public function popContainer():void{
			containers[containers.length-1].removeBackPanel();
			containers[containers.length-1].removeAll();
			containers.pop();
		}
		
		/**
		  Creates from scratch the list of containers according to the number
		  of scenes specified. Before doing so, it clears the current list of containers.
		  It sets current (current container) to zero.
		*/
		public function createContent(numScenes:uint):void{
			this.removeAllContainers();
			current = 0;
			//this.removeSelections(); // If there is something selected, remove it
			while(containers.length < numScenes){
				this.createContainer();
			}
			if(numScenes > 0)
				this.checkUndoRedoActions();
		}
		
		/**
		  Reads an XML file and builds the whole animation from it
		  @param xmlFile XML with all the contents of the animation
		*/
		public function readXML(sceneList:XMLList):void{
			for each (var sceneElement:XML in sceneList){
				this.createContainer();
				this.containers[containers.length -1].setType(Container.typeStringToCode(FileModule.getSceneType(sceneElement)));
				this.containers[containers.length -1].setActionIndex(
						FileModule.completeEditorActions(
						this.containers[containers.length -1].getActions(),
						FileModule.getSceneActions(sceneElement))
						);
			}
			this.buildContainers(true);
		}
		
		/**
		  Adds a child to canvas, if this child is the input text, canvas
		  will focus it automatically
		  @param obj DisplayObject to add
		*/
		public function addChild(obj:DisplayObject):void{
			canvas.addChild(obj);
			if(obj === (input_txt as DisplayObject))
				canvas.stage.focus = obj as TextInput;
		}
		
		/**
		  Adds an event listener to the canvas
		  @param evtType String with the type of the event to listen
		  @param func Function to do when the event is triggered
		  @param useCapture Boolean that determines if the listener will
		     listen to the event in the capture stage (true) or in the
			 propagation stage (false, default)
		*/
		public function addEventListener(evtType:String, func:Function, useCapture:Boolean = false):void{
			canvas.addEventListener(evtType, func, useCapture);
		}
		
		/**
		  Adds a widget to the canvas, that will be placed over the front panel
		  @param widget DisplayObject that will be added to canvas
		*/
		public function addWidget(widget:DisplayObject):void{
			widgets.push(widget);
			canvas.addChild(widgets[widgets.length-1]);//, Math.max(0, canvas.getChildIndex(blockPanel)-1)); 
		}
		
		
		/**
		 Adds a shape object where specified by the mouse click and it returns it
		 @param shapeToCreate String with the shape to create
		 @param firstClusterAction Boolean specifies if it's the first action to the container
		 @return ShapeObject recently created
		*/
		public function addShapeObj(evt:MouseEvent, shapeToCreate:String, firstClusterAction:Boolean = true):ShapeObject{
			return addShapeObjXY(evt.localX, evt.localY, shapeToCreate, firstClusterAction);
		}
		
		/**
		 Adds a shape object where specified by x and y and it returns it
		 @param x Number with X centered position
		 @param y Number with Y centered position
		 @param shapeToCreate String with the shape to create
		 @param firstClusterAction Boolean specifies if it's the first action to the container
		 @return ShapeObject recently created
		*/
		public function addShapeObjXY(posx:Number, posy:Number, shapeToCreate:String, firstClusterAction:Boolean = true):ShapeObject{
			var obj:ShapeObject = containers[current].addShapeXY(shapeToCreate, posx, posy);
			this.addAction(new Action(Action.INSERT,
				obj.getFullID(),
				shapeToCreate,
				obj.x, obj.y), firstClusterAction);
			return obj;
		}
		
		/**
		  Adds blur to the specified shape object
		  @param obj ShapeObject to blur
		  @param blurx Number with the width of the blur, 0 to remove it
		  @param blury Number with the height of the blur, 0 to remove it
		  @param save Boolean Save or not this action to the action list
		  @param firstClusterAction Boolean specifies if it's the first action to the container
		*/
		public function blurShape(obj:ShapeObject, blurx:Number, blury:Number, save:Boolean = false, firstClusterAction:Boolean = true):void{
			var oldblurx:Number = obj.getBlur().x;
			var oldblury:Number = obj.getBlur().y;
			if(oldblurx != blurx || oldblury != blury){
				if(save){
					this.addAction(new Action(
						Action.BLUR,
						obj.getFullID(),
						oldblurx,
						oldblury,
						blurx,
						blury
						), firstClusterAction);
				}
				
				obj.setBlur(blurx, blury);
			}
		}
		
		/**
		  Returns the angle between an object and a 2D point
		  @param obj DisplayObject reference for calculating the angle
		  @param xx Number with X coordinate of the point
		  @param yy Number with Y coordinate of the point
		  @return Number with the angle between 0 and 360
		*/
		public function getAngle(obj:DisplayObject, xx:Number, yy:Number):Number{
			var rot:Number = Math.atan2(yy-obj.y, xx-obj.x)*180/Math.PI;
			if(rot < 0) rot = 360 + rot;
			else if(rot >= 360){
				while(rot >= 360){
					rot = rot-360;
				}
			}
			return rot;
		}
		
		/**
		  Sets the specified ShapeObject to the depth indicated
		  @param obj ShapeObject to change the depth
		  @param depth uint with the new depth
		  @param firstClusterAction Boolean specifies if it's the first action to the container
		*/
		public function setShapeDepth(obj:ShapeObject, depth:uint, firstClusterAction:Boolean = true):void{
			//An object has changed its depth
		  	//CHANGE_DEPTH, "fullID", old_z, new_z
			var oldDepth:uint = containers[current].getShapeDepth(obj);
			var newDepth:uint = containers[current].setShapeDepth(obj, depth);
			if(oldDepth != newDepth){
				this.addAction(new Action(
					Action.CHANGE_DEPTH,
					obj.getFullID(),
					oldDepth,
					newDepth
					), firstClusterAction);
			}
		}
		
		/**
		  Sets the specified ShapeObject painting arrays
		  @param obj ShapeObject to change the depth
		  @param linesArray Array of lines to apply to the shape
		  @param colorArray Array of colors to apply to the shape
		  @param firstClusterAction Boolean specifies if it's the first action to the container
		*/
		public function setShapePaint(obj:ShapeObject, linesArray:Array, colorArray:Array,
									  firstClusterAction:Boolean = true):void{
			
			var array:	Array = new Array();
			var p:		Point;
			
			array.push(colorArray.length); // Number of pairs: color, numcoloredlines
			for each(p in colorArray){
				array.push(p.x);
				array.push(p.y);
			}
			
			array.push(linesArray.length); // Number of lines
			for each(var line:Array in linesArray){
				array.push(line.length); // Number of points on each line
				for each(p in line){
					array.push(p.x);
					array.push(p.y);
				}
			}
			
			obj.setLines(linesArray);
			obj.setColors(colorArray);
			this.addAction(new Action(
					Action.PAINT,
					obj.getFullID(),
					array),
					firstClusterAction);
			obj.repaint();
		}
		
		/**
		  Sets the text of the specified ShapeObject
		  @param obj ShapeObject to change the depth
		  @param txt String with the new text
		  @param firstClusterAction Boolean specifies if it's the first action to the container
		*/
		public function setShapeText(obj:ShapeObject, newtxt:String, firstClusterAction:Boolean = true):void{
			addAction(new Action(
				Action.CHANGE_TEXT, obj.getFullID(),
				obj.getText(),
				newtxt), firstClusterAction);
			obj.setText(newtxt);
		}
		
		/**
		  Removes the specified child from canvas
		  @param obj DisplayObject to remove from canvas
		*/
		public function removeChild(obj:DisplayObject):void{
			canvas.removeChild(obj);
		}
		
		/**
		  Removes an event listener from the canvas
		  @param evtType String with the type of the event to listen
		  @param func Function to do when the event is triggered
		  @param useCapture Boolean that determines if the listener will
		     listen to the event in the capture stage (true) or in the
			 propagation stage (false, default)
		*/
		public function removeEventListener(evtType:String, func:Function, useCapture:Boolean = false):void{
			canvas.removeEventListener(evtType, func, useCapture);
		}
		
		/**
		 Removes all the selections over the objects (SelectionRect)
		*/
		public function removeSelections():void{
			if(selectedShapes.length > 0){
				removeChild(mainSelectionRect);
			}
			while(selectedShapes.length > 0){
				selectedShapes.pop();
				removeChild(selectionRects.pop());
			}
		}
		
		/**
		  Auxiliar function which can remove the object specified
		  from both selection vectors (selectedShapes and selectionRects)
		  Obj must be part of selectedShapes, an error will occur otherwise
		  @param obj ShapeObject to remove from selection lists
		*/
		public function removeShapeFromSelection(obj:ShapeObject):void{
			var index:uint = selectedShapes.indexOf(obj);
			var pos:uint = index;
			var tmp_selectionRect;
			var tmp_shape;
			for(; pos< selectedShapes.length-1; ++pos){
				tmp_shape = selectedShapes[pos];
				selectedShapes[pos] = selectedShapes[pos+1];
				selectedShapes[pos+1] = tmp_shape;
				
				tmp_selectionRect = selectionRects[pos];
				selectionRects[pos] = selectionRects[pos+1];
				selectionRects[pos+1] = tmp_selectionRect;
			}
			selectedShapes.pop();
			removeChild(selectionRects.pop());
			
			if(selectedShapes.length <= 0){
				removeChild(mainSelectionRect);
				removeSelections();
			}else{
				refreshMainSelectionRect();
			}
		}
		
		/**
		  Adds a widget to the canvas, that will be placed over the front panel
		  @param widget DisplayObject that will be added to canvas
		*/
		public function removeWidget(widget:DisplayObject):void{
			//widgets.indexOf(widget);
			trace("CanvasCore: removeWidget() -> Not yet implemented!");
			//canvas.addChild(widgets[widgets.length-1]);//, Math.max(0, canvas.getChildIndex(blockPanel)-1)); 
		}
		
		/**
		  Rotates the specified Shape to angle, calculated with Atan2,
		  and inserts the action
		  @param shap ShapeObject to rotate
		  @param yy Number with Y vector of the rotation
		  @param xx Number with X vector of the rotation
		  @param save Boolean Save or not this action to the action list
		  @param firstClusterAction Boolean specifies if it's the first action to the container (ignored if save == false)
		*/
		public function rotateShapeAtan2(shap:ShapeObject, yy:Number, xx:Number, save:Boolean = false, firstClusterAction:Boolean = true):void{
			var rot:Number = Math.atan2(yy, xx)*180/Math.PI;
			if(rot < 0) rot = 360 + rot;
			else if(rot >= 360){
				while(rot >= 360){
					rot = rot-360;
				}
			}
			
			if(save){
				this.addAction(new Action(Action.ROTATE,
									  shap.getFullID(),
									  shap.rotation,
									  rot),
						   firstClusterAction
						   );
			}
			shap.rotation = rot;
		}
		
		/**
		  Rotates the specified Shape to specified angle
		  and inserts the action if wanted
		  @param shap ShapeObject to rotate
		  @param rot Number angle to rotate
		  @param save Boolean Save or not this action to the action list
		  @param firstClusterAction Boolean specifies if it's the first action to the container (ignored if save == false)
		*/
		public function rotateShape(shap:ShapeObject, rot:Number, save:Boolean = false, firstClusterAction:Boolean = true):void{
			if(rot < 0) rot = 360 + rot;
			else if(rot >= 360){
				while(rot >= 360){
					rot = rot-360;
				}
			}
			
			if(save){
				this.addAction(new Action(Action.ROTATE,
									  shap.getFullID(),
									  shap.rotation,
									  rot),
						   firstClusterAction
						   );
			}
			shap.rotation = rot;
		}
		
		/**
		  Scales the Shape to the specified, adding the corresponding action
		  @param shap ShapeObject to scale
		  @param scalex Number with scale X (relative to original size)
		  @param scaley Number with scale Y (relative to original size)
		  @param save Boolean Save or not this action to the action list
		  @param firstClusterAction Boolean specifies if it's the first action to the container (ignored if save == false)
		*/
		public function scaleShape(shap:ShapeObject, scalex:Number, scaley:Number, save:Boolean = false, firstClusterAction:Boolean = true):void{
			
			if(save){
				this.addAction(new Action(Action.SCALE,
									  shap.getFullID(),
									  shap.scaleX, shap.scaleY,
									  scalex, scaley),
						   firstClusterAction
						   );
			}
			shap.scaleX = scalex;
			shap.scaleY = scaley;
		}
		
		/**
		  Empties the containers list deleting also all of its children and eventListeners
		*/
		public function removeAllContainers():void{
			while(containers.length > 0){
				(containers.pop()).removeAll();
			}
		}
		
		/**
		  Shows or hides the blockPanel in the Canvas
		  @param doIt Boolean to show (true) or hide (false)
		*/
		public function showBlockPanel(doIt:Boolean):void{
			if(doIt){
				blockPanel.x = 0;
			}else{
				blockPanel.x = canvas.width;
			}
		}
		
		/**
		  Adds the specified action to the current container
		  @param act Action to be added
		  @param firstClusterAction Boolean specifies if it's the first action to the container
		*/
		public function addAction(act:Action, firstClusterAction:Boolean = true):void{
			containers[current].addAction(act, firstClusterAction);
			redoFunction(false);
			undoFunction(true);
		}
		
		/**
		  Cleans and fills the shapeShadowList with all the information
		  regarding shapeObjects of the previous frame, if it's not the first one
		*/
		public function fillShadowList():void{
			shapeShadows.clear();
			if(current > 0){
				for(var i:uint = 0; i<containers[current-1].getNumShapes(); ++i){
					shapeShadows.push(containers[current-1].getShapeAt(i));
				}
			}
		}
		
		/**
		  Calculates the distance in pixels between two shapeObjects
		  @param shap1 DisplayObject number 1
		  @param shap1 DisplayObject number 2
		  @return Number with the absolute value of the distance
		*/
		public function distanceOf(shap1:DisplayObject, shap2:DisplayObject):Number{
			var dist:Number = 0;
			dist = Math.abs(Math.sqrt((shap2.x - shap1.x)*(shap2.x - shap1.x)+
										(shap2.y - shap1.y)*(shap2.y - shap1.y))
							);
			return dist;
		}
		
		/**
		  Calculates the distance in pixels between a shapeObject and two coordinates
		  @param shap1 DisplayObject of reference
		  @param xx Number with X coordinates
		  @param yy Number with Y coordinates
		  @return Number with the absolute value of the distance
		*/
		public function distanceOfNum(shap1:DisplayObject, xx:Number, yy:Number):Number{
			var dist:Number = 0;
			dist = Math.abs(Math.sqrt((xx - shap1.x)*(xx - shap1.x)+
										(yy - shap1.y)*(yy - shap1.y))
							);
			return dist;
		}
		
		/**
		  Encapsulation of invalidate actions that meet the specifications
		  from the current container
		  @param acttype uint with the type of action to invalidate
		  @param shapID String with the Shape ID
		  @return Array with invalidated indexes of actions, null if none were invalidated
		*/
		public function invalidateActions(acttype:uint, shapID:String):Array{
			return containers[current].invalidateActions(acttype, shapID);
		}
		
		/**
		  Undoes the last action
		*/
		public function undo():void{
			removeSelections();
			containers[current].undo();
			repaint();
			checkUndoRedoActions();
		}
		
		/**
		  Redoes the last undone action (if there is one)
		*/
		public function redo():void{
			removeSelections();
			containers[current].redo();
			repaint();
			checkUndoRedoActions();
		}
		
		/**
		  Repaints everything that needs to be repainted
		*/
		public function repaint():void{
			drawShapeShadows(backPanel);
		}
		
		/**
		  Sets the selection request function, this is a method to allow the
		  canvas to request the change of the tool to the select tool and
		  automatically edit the last shape
		  @param selF Function to call to request the change of the tool
		*/
		public function setChangeToolFunction(selF:Function):void{
			changeToolFunction = selF;
		}
		
		/**
		  Shows the undo button as available
		  @param available Boolean true (default) show it, false to show it is
		  	not available
		*/
		public function setUndoAvailable(available:Boolean = true):void{
			undoFunction(available);
		}
		
		/**
		  Shows the redo button as available
		  @param available Boolean true (default) show it, false to show it is
		  	not available
		*/
		public function setRedoAvailable(available:Boolean = true):void{
			redoFunction(available);
		}
		
		/**
		  Checks if the current container has actions to redo or undo
		*/
		public function checkUndoRedoActions():void{
			if(containers[current].getActions().size() > 0)
				setUndoAvailable(true);
			else
				setUndoAvailable(false);
			
			if(containers[current].getNumRedoActions() > 0)
				setRedoAvailable(true);
			else
				setRedoAvailable(false);
		}
		
		// --------------------- PRIVATE FUNCTIONS --------------------- //
		
		/**
		  Draws all the shadows of all the shapes of the container before
		*/
		private function drawShapeShadows(panel:Panel):void{
			var shap:ShapeObject;
			var offsetColor:uint;
			var color:uint;
			
			panel.graphics.clear();
			if(current > 0){
				//offsetColor =  Math.round(16777215/containers[current-1].getNumShapes());
				//if(offsetColor == 0) offsetColor = 1;
				
				color = ShapeObjectDatabase.getBasicColor(0);
				panel.graphics.lineStyle(1, color, 0.5);
				for(var i:uint = 0; i<containers[current-1].getNumShapes(); ++i){
					shap = containers[current-1].getShapeAt(i);
					if(! shap.isRemoved()){
						drawShapeShadow(shap, panel);
						drawShapeCurvedLine(shap,
							containers[current].getShape(shap.getFullID()),
							panel);
						color = ShapeObjectDatabase.getBasicColor(i+1);
						//color = color+offsetColor;
						//if(color > 0xFFFFFF) color = 0x000000;
						panel.graphics.lineStyle(1, color, 0.5);
					}
				}
			}
		}
		
		/**
		  Draws a shadow of the shape over the specified panel, graphic attributes
		  are NOT defined here
		  @param shap ShapeObject to represent the shadow
		  @param panel Panel to paint
		*/
		private function drawShapeShadow(shap:ShapeObject, panel:Panel):void{

			switch(ShapeObjectDatabase.getBasicForm(shap)){
				case ShapeObjectDatabase.SQUARE:
					panel.graphics.drawRect(shap.x - shap.width/2,
											shap.y - shap.height/2,
											shap.width, shap.height);
					break;
				case ShapeObjectDatabase.ROUNDRECT:
					panel.graphics.drawRoundRect(shap.x - shap.width/2,
												shap.y - shap.height/2,
												shap.width, shap.height,
												shap.width/4, shap.height/4);
					break;
				case ShapeObjectDatabase.CIRCLE:
					panel.graphics.drawCircle(shap.x,
											  shap.y,
											  shap.width/2);
					break;
				case ShapeObjectDatabase.ARROW:
					panel.graphics.moveTo(shap.x-shap.width/5, shap.y);
					panel.graphics.lineTo(shap.x+shap.width/5, shap.y);
					panel.graphics.lineTo(shap.x+shap.width/10, shap.y-shap.height/5);
					panel.graphics.moveTo(shap.x+shap.width/5, shap.y);
					panel.graphics.lineTo(shap.x+shap.width/10, shap.y+shap.height/5);
					break;
			}
		}
		
		/**
		  Draws a curved line between two shapes over the panel, graphic attributes
		  are NOT defined here
		  @param shapOrig ShapeObject origin
		  @param shapDest ShapeObject destination
		  @param panel Panel to paint
		*/
		private function drawShapeCurvedLine(shapOrig:ShapeObject, shapDest:ShapeObject, panel:Panel):void{
			panel.graphics.moveTo(shapOrig.x,
								  shapOrig.y);
			panel.graphics.curveTo((shapOrig.x + shapDest.x)/2,
									(shapOrig.y + shapDest.y)/2 - 30,
									shapDest.x,
									shapDest.y);
		}
		
	}
}